from collections import defaultdict

from django.db import transaction
from django.db.models import F, Q
from django.db.models.functions import Length

from gis.common import track_logging
from gis.common.django_ext.models import paginate
from gis.common.exceptions import BizException, ILLEGAL_PARAMETER
from itpay.core.agency import agency_stat_service, agency_service, blacklist_service
from itpay.core.agency.const import (
    PayMethodType,
    ReceiptMethodType,
    ReceiptRule,
    ReceiptType,
    PayType,
    BankCode,
    PayRule,
    RECEIPT_ADDRESS_LIMIT_MSG,
)
from itpay.core.agency.errors import (
    ERROR_RECEIPT_CONF_NOT_SUPPORTED,
    ERROR_AGENT_DISABLE,
    ERROR_USER_IN_BLACKLIST,
    ERROR_NEED_MANUAL_SEND_RECEIPT,
    ERROR_USER_NO_MATCH_RECEIPT_ADDRESS,
    ERROR_RECEIPT_ADDRESS_NOT_EXISTS,
)
from itpay.core.agency.models import ReceiptConf, ReceiptAddress
from itpay.core.open.im_service import send_im_message_to_agency
from itpay.core.user import user_service
from itpay.core.user.const import UserLevel

_LOGGER = track_logging.getLogger(__name__)


def get_all_receipt_conf():
    """
    Deprecated
    查询收款账户规则配置
    """
    return [e.to_dict() for e in ReceiptConf.objects.all()]


def get_receipt_conf(pay_method: PayMethodType, receipt_method: ReceiptMethodType):
    """
    Deprecated
    根据付款方式和收款方式获取唯一规则配置
    :return:
    """
    assert isinstance(pay_method, PayMethodType)
    assert isinstance(receipt_method, ReceiptMethodType)
    for e in get_all_receipt_conf():
        if (
            e["pay_method"] == pay_method.value
            and e["receipt_method"] == receipt_method.value
        ):
            return e


def update_receipt_conf(
    conf_id,
    im_amount_tip,
    im_pay_tip,
    im_pay_success_tip,
    page_top_tip,
    page_bottom_tip,
):
    """
    Deprecated
    更新收款配置
    """
    m_conf = ReceiptConf.objects.get(pk=conf_id)
    m_conf.im_amount_tip = im_amount_tip
    m_conf.im_pay_tip = im_pay_tip
    m_conf.im_pay_success_tip = im_pay_success_tip
    m_conf.page_top_tip = page_top_tip
    m_conf.page_bottom_tip = page_bottom_tip
    m_conf.save()


def get_receipt_conf_by_id(conf_id):
    """
    Deprecated
    :param conf_id:
    :return:
    """
    conf = ReceiptConf.objects.filter(pk=conf_id).first()
    if not conf:
        raise BizException(ERROR_RECEIPT_CONF_NOT_SUPPORTED)
    return conf.to_dict()


def create_receipt_address(
    agency_id,
    receipt_type,
    receipt_method,
    account=None,
    account_qr_code=None,
    nickname=None,
    ali_zkl=None,
    qr_code=None,
    mch_name=None,
    can_huabei=None,
    can_credit=None,
    can_person=None,
    first_name=None,
    last_name=None,
    bank_code=None,
    bank_branch=None,
    phone=None,
    status=False,
    auto_arrival=False,
    daily_amount_limit=None,
    min_user_level=None,
    remark=None,
):
    """
    创建收款账户
    不同的收款类型，需要填充的字段不同
    """
    assert agency_id > 0

    m_receipt = _validate_receipt_account(
        receipt_type,
        receipt_method,
        account,
        account_qr_code,
        nickname,
        ali_zkl,
        qr_code,
        mch_name,
        can_huabei,
        can_credit,
        can_person,
        first_name,
        last_name,
        bank_code,
        bank_branch,
        phone,
        status,
        auto_arrival,
        daily_amount_limit,
        min_user_level,
        remark,
    )

    with transaction.atomic():
        m_receipt.agency_id = agency_id
        m_receipt.save()

        update_receipt_stat(agency_id)

    return m_receipt.to_dict()


# 验证收款账户信息完整及合法性
def _validate_receipt_account(
    receipt_type,
    receipt_method,
    account,
    account_qr_code,
    nickname,
    ali_zkl,
    qr_code,
    mch_name,
    can_huabei,
    can_credit,
    can_person,
    first_name,
    last_name,
    bank_code,
    bank_branch,
    phone,
    status,
    auto_arrival,
    daily_amount_limit,
    min_user_level,
    remark,
):
    assert isinstance(receipt_type, ReceiptType)
    assert isinstance(receipt_method, ReceiptMethodType)
    assert isinstance(min_user_level, UserLevel)

    receipt_address = ReceiptAddress(
        receipt_type=receipt_type.value,
        receipt_method=receipt_method.value,
        status=status if status is not None else False,
        auto_arrival=auto_arrival if auto_arrival is not None else False,
        daily_amount_limit=daily_amount_limit,
        min_user_level=min_user_level.value,
        remark=remark,
    )
    pay_types = []
    pay_methods = []

    if receipt_method not in ReceiptRule[receipt_type]:
        raise BizException(ILLEGAL_PARAMETER, "收款账户类型和收款方式不匹配")

    if daily_amount_limit is not None and daily_amount_limit < 0:
        raise BizException(ILLEGAL_PARAMETER, "每日收款上限不能小于0")

    if auto_arrival:
        if receipt_type != ReceiptType.BANK_CARD or not phone:
            raise BizException(ILLEGAL_PARAMETER, "目前自助上分只支持银行卡收款，且需要绑定手机号")

    if receipt_method == ReceiptMethodType.ALIPAY_PERSON:
        if not (account and first_name and last_name):
            raise BizException(ILLEGAL_PARAMETER, "支付宝个人收款必需字段：账号，姓，名")
        pay_types.append(PayType.ALIPAY)
        pay_methods.append(PayMethodType.ALIPAY_PERSON_ACCOUNT)
        receipt_address.account = account
        receipt_address.first_name = first_name
        receipt_address.last_name = last_name
        if ali_zkl:
            pay_methods.append(PayMethodType.ALIPAY_PERSON_ZKL)
            receipt_address.ali_zkl = ali_zkl
        if account_qr_code:
            pay_methods.append(PayMethodType.ALIPAY_PERSON_ACCOUNT_QR_CODE)
            receipt_address.account_qr_code = account_qr_code
        if qr_code:
            pay_methods.append(PayMethodType.ALIPAY_PERSON_QR_CODE)
            receipt_address.qr_code = qr_code

    elif receipt_method == ReceiptMethodType.ALIPAY_MCH:
        if not (
            mch_name
            and qr_code is not None
            and can_huabei is not None
            and can_credit is not None
            and can_person is not None
        ):
            raise BizException(
                ILLEGAL_PARAMETER, "支付宝商家收款必需字段：商家名称，收款二维码, 是否支持花呗，是否支持信用卡，是否支持个人支付宝"
            )
        receipt_address.mch_name = mch_name
        receipt_address.qr_code = qr_code
        receipt_address.can_huabei = can_huabei
        receipt_address.can_credit = can_credit
        receipt_address.can_person = can_person
        if can_huabei:
            pay_types.append(PayType.HUABEI)
            pay_methods.append(PayMethodType.ALIPAY_HUABEI)
        if can_credit:
            pay_types.append(PayType.CREDIT)
            pay_methods.append(PayMethodType.ALIPAY_CREDIT)
        if can_person:
            pay_types.append(PayType.ALIPAY)
            pay_methods.append(PayMethodType.ALIPAY_PERSON_QR_CODE)

    elif receipt_method == ReceiptMethodType.WECHAT_PERSON:
        if not (account and nickname and qr_code):
            raise BizException(ILLEGAL_PARAMETER, "微信个人收款必需字段：账号，昵称，二维收款码")
        pay_types.append(PayType.WECHAT)
        pay_methods.append(PayMethodType.WECHAT_PERSON_QR_CODE)
        receipt_address.account = account
        receipt_address.nickname = nickname
        receipt_address.qr_code = qr_code

    elif receipt_method == ReceiptMethodType.WECHAT_MCH:
        if not (
            mch_name and qr_code and can_credit is not None and can_person is not None
        ):
            raise BizException(
                ILLEGAL_PARAMETER, "微信商家收款必需字段：商家名称，收款二维码, 是否支持信用卡，是否支持个人微信"
            )
        receipt_address.mch_name = mch_name
        receipt_address.qr_code = qr_code
        receipt_address.can_credit = can_credit
        receipt_address.can_person = can_person
        if can_credit:
            pay_types.append(PayType.CREDIT)
            pay_methods.append(PayMethodType.WECHAT_CREDIT)
        if can_person:
            pay_types.append(PayType.WECHAT)
            pay_methods.append(PayMethodType.WECHAT_PERSON_QR_CODE)

    elif receipt_method == ReceiptMethodType.BANK_CARD:
        if not (account and first_name and last_name and bank_code and bank_branch):
            assert isinstance(bank_code, BankCode)
            raise BizException(ILLEGAL_PARAMETER, "银行卡收款必需字段：银行卡号，姓，名，开户银行，开户支行")
        if auto_arrival and not phone:
            raise BizException(ILLEGAL_PARAMETER, "银行卡自助上分收款需要填写绑定手机号")
        receipt_address.account = account
        receipt_address.first_name = first_name
        receipt_address.last_name = last_name
        receipt_address.phone = phone
        receipt_address.bank_code = bank_code.value
        receipt_address.bank_branch = bank_branch
        pay_types.append(PayType.BANK_CARD)
        pay_methods.append(PayMethodType.BANK_CARD)
        pay_types.append(PayType.ALIPAY)
        pay_methods.append(PayMethodType.ALIPAY_PERSON_BANK_CARD)
        pay_types.append(PayType.WECHAT)
        pay_methods.append(PayMethodType.WECHAT_PERSON_BANK_CARD)
    else:
        raise Exception("unsupported receipt method")

    assert pay_types, "must contains at least one pay type"
    assert pay_methods, "must contains at least one pay method"

    receipt_address.pay_types = [e.value for e in pay_types]
    receipt_address.pay_methods = [e.value for e in pay_methods]

    return receipt_address


def delete_receipt_address(receipt_id):
    """
    删除收款账户
    """
    receipt = _get_receipt_address(receipt_id)
    receipt.deleted = True
    receipt.save()

    update_receipt_stat(receipt.agency_id)


def update_receipt_address_status(receipt_id, status):
    """
    更新启用状态
    """
    receipt = _get_receipt_address(receipt_id)
    receipt.status = status
    receipt.save()

    update_receipt_stat(receipt.agency_id)

    return _fill_receipt_quotas(receipt)


def update_receipt_address(
    receipt_id,
    account=None,
    account_qr_code=None,
    nickname=None,
    ali_zkl=None,
    qr_code=None,
    mch_name=None,
    can_huabei=None,
    can_credit=None,
    can_person=None,
    first_name=None,
    last_name=None,
    bank_code=None,
    bank_branch=None,
    phone=None,
    status=False,
    auto_arrival=False,
    daily_amount_limit=None,
    min_user_level=None,
    remark=None,
):
    """
    编辑收款账户
    """

    m_receipt = _get_receipt_address(receipt_id)
    updated_receipt = _validate_receipt_account(
        ReceiptType(m_receipt.receipt_type),
        ReceiptMethodType(m_receipt.receipt_method),
        account,
        account_qr_code,
        nickname,
        ali_zkl,
        qr_code,
        mch_name,
        can_huabei,
        can_credit,
        can_person,
        first_name,
        last_name,
        bank_code,
        bank_branch,
        phone,
        status,
        auto_arrival,
        daily_amount_limit,
        min_user_level,
        remark,
    )
    m_receipt.pay_types = updated_receipt.pay_types
    m_receipt.pay_methods = updated_receipt.pay_methods
    m_receipt.account = updated_receipt.account
    m_receipt.account_qr_code = updated_receipt.account_qr_code
    m_receipt.nickname = updated_receipt.nickname
    m_receipt.ali_zkl = updated_receipt.ali_zkl
    m_receipt.qr_code = updated_receipt.qr_code
    m_receipt.mch_name = updated_receipt.mch_name
    m_receipt.can_huabei = updated_receipt.can_huabei
    m_receipt.can_credit = updated_receipt.can_credit
    m_receipt.can_person = updated_receipt.can_person
    m_receipt.first_name = updated_receipt.first_name
    m_receipt.last_name = updated_receipt.last_name
    m_receipt.bank_code = updated_receipt.bank_code
    m_receipt.bank_branch = updated_receipt.bank_branch
    m_receipt.phone = updated_receipt.phone
    m_receipt.status = updated_receipt.status
    m_receipt.auto_arrival = updated_receipt.auto_arrival
    m_receipt.min_user_level = updated_receipt.min_user_level
    m_receipt.daily_amount_limit = updated_receipt.daily_amount_limit
    m_receipt.remark = updated_receipt.remark
    m_receipt.save()

    update_receipt_stat(m_receipt.agency_id)

    return m_receipt.to_dict()


def update_receipt_part_field(receipt_id, status=None, auto_arrival=None, phone=None):
    with transaction.atomic():
        m_receipt = _get_receipt_address(receipt_id)
        if status is not None:
            m_receipt.status = status
        if auto_arrival is not None:
            m_receipt.auto_arrival = auto_arrival
        if phone is not None:
            m_receipt.phone = phone
        m_receipt.save()

        _validate_receipt_account(
            ReceiptType(m_receipt.receipt_type),
            ReceiptMethodType(m_receipt.receipt_method),
            m_receipt.account,
            m_receipt.account_qr_code,
            m_receipt.nickname,
            m_receipt.ali_zkl,
            m_receipt.qr_code,
            m_receipt.mch_name,
            m_receipt.can_huabei,
            m_receipt.can_credit,
            m_receipt.can_huabei,
            m_receipt.first_name,
            m_receipt.last_name,
            BankCode(m_receipt.bank_code) if m_receipt.bank_code else None,
            m_receipt.bank_branch,
            m_receipt.phone,
            m_receipt.status,
            m_receipt.auto_arrival,
            m_receipt.daily_amount_limit,
            UserLevel(m_receipt.min_user_level),
            m_receipt.remark,
        )
        update_receipt_stat(m_receipt.agency_id)


def get_receipt_address(receipt_id, fill_quotas=True):
    m_receipt = _get_receipt_address(receipt_id)
    if fill_quotas:
        return _fill_receipt_quotas(m_receipt)
    else:
        return m_receipt.to_dict()


def list_receipt_addresses(
    page_no,
    page_size,
    id=None,
    agency_id=None,
    agency_name=None,
    parent_id=None,
    parent_name=None,
    pay_types=None,
    pay_method=None,
    receipt_type=None,
    receipt_method=None,
    status=None,
    mch_id=None,
    auto_arrival=None,
    reached_daily_limit=None,
    available=None,
    receipt_name=None,
    min_user_level=None,
):
    query = ReceiptAddress.objects.filter(deleted=False)
    if id:
        query = query.filter(pk=id)
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if agency_name:
        query = query.filter(agency__user_name__contains=agency_name)
    if parent_name:
        query = query.filter(agency__parent__user_name__contains=parent_name)
    if pay_types:
        q = None
        for pay_type in pay_types:
            if q is None:
                q = Q(pay_types__contains="|{}|".format(pay_type.value))
            else:
                q = q | Q(pay_types__contains="|{}|".format(pay_type.value))
        query = query.filter(q)
    if pay_method:
        query = query.filter(pay_methods__contains="|{}|".format(pay_method.value))
    if receipt_type:
        query = query.filter(receipt_type=receipt_type.value)
    if receipt_method:
        query = query.filter(receipt_method=receipt_method.value)
    if status is not None:
        query = query.filter(status=status)
    if auto_arrival is not None:
        query = query.filter(auto_arrival=auto_arrival)
    if mch_id:
        query = query.filter(agency__mchs__exact=mch_id)
    if reached_daily_limit is not None:
        if reached_daily_limit:
            query = query.filter(
                Q(daily_amount__gte=F("daily_amount_limit"))
                & Q(daily_amount_limit__isnull=False)
            )
        else:
            query = query.filter(
                Q(daily_amount__lt=F("daily_amount_limit"))
                | Q(daily_amount_limit__isnull=True)
            )
    if available is not None:
        if available:
            query = query.filter(
                Q(daily_amount__lt=F("daily_amount_limit"))
                | Q(daily_amount_limit__isnull=True),
                status=True,
            )
        else:
            query = query.filter(
                (
                    Q(daily_amount__gte=F("daily_amount_limit"))
                    & Q(daily_amount_limit__isnull=False)
                )
                | Q(status=False)
            )

    if receipt_name is not None:
        query = query.extra(
            where=[
                "(CONCAT(first_name ,last_name)  =  '{receipt_name}') "
                "OR `agency_receiptaddress`.nickname = '{receipt_name}'  or mch_name =  '{receipt_name}'".format(
                    receipt_name=receipt_name
                )
            ]
        )

    if min_user_level is not None:
        query = query.filter(min_user_level=min_user_level)

    return (
        query.count(),
        [_fill_receipt_quotas(e) for e in paginate(query, page_no, page_size)],
    )


def get_agency_valid_pay_types_by_user(agency_id, mch_id, user_id):
    """
    获取用户可用的支付方式列表
    :return [1, 2, ...]
    """
    user = user_service.get_user_info(mch_id, user_id)
    agency = agency_service.get_agency_by_id(agency_id)
    query = ReceiptAddress.objects.filter(
        agency_id=agency_id,
        min_user_level__lte=user["level"],
        status=True,
        deleted=False,
    )
    query = query.filter(
        Q(daily_amount_limit__isnull=True) | Q(daily_amount__lt=F("daily_amount_limit"))
    )
    if not agency["working"]:
        query = query.filter(auto_arrival=True)
    return list(set().union(*[e.pay_types for e in query]))


def match_receipt_address_pay_type(
    agency_id, mch_id, user_id, pay_type, check_auto_show=False
):
    """
    根据用户id匹配某个代理可用的收款账户

    匹配规则：
    1. 检查代理拉黑用户
    2. 代理是否禁用
    3. 收款账户对用户等级要求
    4. 收款账户单日收款额度限制
    5. 优先匹配同一收款账户

    """
    assert agency_id > 0
    assert isinstance(pay_type, PayType)
    assert mch_id > 0
    assert user_id

    agency = agency_service.get_agency_by_id(agency_id)
    # 检查代理是否禁用
    if not agency["enable"]:
        raise BizException(ERROR_AGENT_DISABLE)

    # 检查代理是否拉黑用户
    if blacklist_service.check_in_blacklist(agency_id, mch_id, user_id):
        raise BizException(ERROR_USER_IN_BLACKLIST)

    user = user_service.get_user_info(mch_id, user_id)

    # 检查用户的级别是否达到代理设置的自动匹配等级要求
    if check_auto_show and user["level"] < agency["auto_show_level"]:
        raise BizException(ERROR_NEED_MANUAL_SEND_RECEIPT)

    query = ReceiptAddress.objects.filter(
        agency_id=agency_id,
        min_user_level__lte=user["level"],
        pay_types__contains="|{}|".format(pay_type.value),
        status=True,
        deleted=False,
    )
    query = query.filter(
        Q(daily_amount_limit__isnull=True) | Q(daily_amount__lt=F("daily_amount_limit"))
    )

    # 如果当前代理下班中，则只下发自动上分的收款账户
    if check_auto_show and not agency["working"]:
        _LOGGER.info(
            "user request offline agency receipts, mch: {}, user: {}, agency: {}".format(
                mch_id, user_id, agency_id
            )
        )
        query = query.filter(auto_arrival=True)

    m_receipt = query.order_by(
        Length("pay_types").asc(), "-min_user_level", "?"
    ).first()

    if not m_receipt:
        raise BizException(ERROR_USER_NO_MATCH_RECEIPT_ADDRESS)

    receipt = m_receipt.to_dict()
    receipt["pay_type"] = pay_type

    for k, v in PayRule.items():
        if k == pay_type:
            for vv in v:
                if vv.value in m_receipt.pay_methods:
                    receipt["pay_method"] = vv.value
                    break

    assert receipt.get("pay_method")
    return receipt


def _get_receipt_address(receipt_id, check_deleted=True):
    query = ReceiptAddress.objects.filter(pk=receipt_id)
    if check_deleted:
        query = query.filter(deleted=False)
    receipt = query.first()
    if not receipt:
        raise BizException(ERROR_RECEIPT_ADDRESS_NOT_EXISTS)
    return receipt


def _fill_receipt_quotas(m_receipt):
    receipt = m_receipt.to_dict()
    # receipt['quotas'] = [q.to_dict(fields=['amount', 'real_amount', 'qr_code']) for q in
    #                      m_receipt.receiptaddressquota_set.all().order_by('amount')]
    return receipt


def update_receipt_daily_amount(receipt_id, incr_amount):
    rows = ReceiptAddress.objects.filter(pk=receipt_id).update(
        daily_amount=F("daily_amount") + incr_amount
    )
    if rows:
        m_receipt = _get_receipt_address(receipt_id)
        if (
            m_receipt.daily_amount_limit is not None
            and m_receipt.daily_amount >= m_receipt.daily_amount_limit
        ):
            update_receipt_stat(m_receipt.agency_id)

            try:
                send_im_message_to_agency(
                    m_receipt.agency_id,
                    RECEIPT_ADDRESS_LIMIT_MSG.format(receipt_id=receipt_id),
                )
            except Exception:
                _LOGGER.info(
                    "send agency message fail {}".format(m_receipt.agency_id),
                    exc_info=True,
                )


def clear_all_receipt_daily_amount():
    """
    清除所有账户的daily_amount数据，一般是按天周期执行
    """
    ReceiptAddress.objects.all().update(daily_amount=0)
    for agency_id in ReceiptAddress.objects.values_list("agency_id").distinct():
        update_receipt_stat(agency_id[0])


def update_receipt_stat(agency_id):
    """
    更新代理收款统计数据
    """
    receipts = ReceiptAddress.objects.filter(
        agency_id=agency_id, deleted=False, status=True
    )
    receipts = receipts.filter(
        Q(daily_amount_limit__isnull=True) | Q(daily_amount__lt=F("daily_amount_limit"))
    )
    support_auto_arrival = False
    auto_arrival_min_level = None
    pay_type_min_level_map = defaultdict(list)  # {<pay_type>: [min_user_level, ...]}
    for rp in receipts:
        for pt in rp.pay_types:
            pay_type_min_level_map[pt].append(rp.min_user_level)
        if rp.auto_arrival:
            support_auto_arrival = True
            auto_arrival_min_level = (
                min(rp.min_user_level, auto_arrival_min_level)
                if auto_arrival_min_level is not None
                else rp.min_user_level
            )

    agency_stat_service.update_stat(
        agency_id,
        pay_types={k: min(v) for k, v in pay_type_min_level_map.items()}
        if pay_type_min_level_map
        else agency_stat_service.NoneValue,
        support_auto_arrival=support_auto_arrival,
        auto_arrival_min_level=auto_arrival_min_level,
    )


def random_receipt(agency_ids, mch_id, user_id, pay_type, recharge_success_amount=None):
    """
    根据代理id,支付方式等，随机一个收款方式
    """
    assert isinstance(agency_ids, list)

    if recharge_success_amount is None:
        user_level = user_service.get_user_info(mch_id, user_id)["level"]
    else:
        user_level = user_service.transform_user_level(recharge_success_amount)

    query = ReceiptAddress.objects.filter(
        agency_id__in=agency_ids,
        min_user_level__lte=user_level,
        pay_types__contains="|{}|".format(pay_type.value),
        status=True,
        deleted=False,
    )
    query = query.filter(
        Q(daily_amount_limit__isnull=True) | Q(daily_amount__lt=F("daily_amount_limit"))
    )

    m_receipt = query.order_by(
        Length("pay_types").asc(), "-min_user_level", "?"
    ).first()

    if not m_receipt:
        raise BizException(ERROR_USER_NO_MATCH_RECEIPT_ADDRESS)

    receipt = m_receipt.to_dict()
    receipt["pay_type"] = pay_type

    for k, v in PayRule.items():
        if k == pay_type:
            for vv in v:
                if vv.value in m_receipt.pay_methods:
                    receipt["pay_method"] = vv.value
                    break

    assert receipt.get("pay_method")
    return receipt
